<?php

namespace app\common\lib;


use app\model\pay\ApplePayNotifyLogModel;
use app\model\pay\ApplepayRequestLogModel;

class ApplePay
{
    public $dataRaw;

    public $password;

    protected $urlBuy     = "https://buy.itunes.apple.com/verifyReceipt";

    protected $urlSandbox = "https://sandbox.itunes.apple.com/verifyReceipt";

    protected static $instance;

    public static function getInstance()
    {
        //        if(is_object(self::$instance)){
//            return self::$instance;
//        }


        return new self();
    }

    /**
     * 验证AppStore内付
     * @param  $receiptData 付款后凭证
     * @param  $tradeNo 交易号
     * @return mixed
     *
     * 21000 App Store不能读取你提供的JSON对象
     * 21002 receipt-data域的数据有问题
     * 21003 receipt无法通过验证
     * 21004 提供的shared secret不匹配你账号中的shared secret
     * 21005 receipt服务器当前不可用
     * 21006 receipt合法，但是订阅已过期。服务器接收到这个状态码时，receipt数据仍然会解码并一起发送
     * 21007 receipt是Sandbox receipt，但却发送至生产系统的验证服务
     * 21008 receipt是生产receipt，但却发送至Sandbox环境的验证服务
     * $receipt_data 苹果返回的支付凭证
     * 正式 ： https://buy.itunes.apple.com/verifyReceipt
     * 沙箱 ： https://sandbox.itunes.apple.com/verifyReceipt
     */
    public function validate($receiptData,$tradeNo)
    {
        $this->password = config('param.apple_pay_password');

        $data = [
            "receipt-data"             => $receiptData,
            'password'                 => $this->password,
            'exclude-old-transactions' => true
        ];

        $header = [
            'Content-Type: application/json; charset=utf-8'
        ];
        $result = Common::curlApi($this->urlBuy, $data, true, $header);
        $this->insertLog($data,$result,$this->urlBuy,'一次生产验证请求',$tradeNo);

        if (isset($result['status']) && $result['status'] == '21007') {
            //如果是沙盒数据 则验证沙盒模式
            $result = Common::curlApi($this->urlSandbox, $data, true, $header);
            $this->insertLog($data,$result,$this->urlSandbox,'二次测试验证请求',$tradeNo);
        }

        return $result;
    }

    protected function insertLog($request,$result,$url,$name,$tradeNo)
    {
        $data['request_param'] = $request;
        $data['reponse_result'] = $result;
        $data['request_url'] = $url;
        $data['request_name'] = $name;
        $data['trade_no'] = $tradeNo??'';

        return ApplepayRequestLogModel::getInstance()->insertData($data);
    }

    public function notify($postDataContent)
    {
        $postData        = json_decode($postDataContent, true);
        $data            = $this->verifyToken($postData['signedPayload']);
        if ($data) {
            /*
            https://developer.apple.com/documentation/appstoreservernotifications/responsebodyv2decodedpayload
            signedPayload 解密后 属性有 notificationType subtype  notificationUUID  data  version
            通知类型 notificationType
            https://developer.apple.com/documentation/appstoreservernotifications/notificationtype
            CONSUMPTION_REQUEST 表示客户针对消耗品内购发起退款申请
            DID_CHANGE_RENEWAL_PREF 对其订阅计划进行了更改 如果subtype是UPGRADE，则用户升级了他们的订阅;如果subtype是DOWNGRADE，则用户将其订阅降级或交叉分级
            DID_CHANGE_RENEWAL_STATUS 通知类型及其subtype指示用户对订阅续订状态进行了更改
            DID_FAIL_TO_RENEW 一种通知类型及其subtype指示订阅由于计费问题而未能续订
            DID_RENEW 一种通知类型，连同其subtype指示订阅成功续订
            EXPIRED 一种通知类型及其subtype指示订阅已过期
            GRACE_PERIOD_EXPIRED 表示计费宽限期已结束，无需续订，因此您可以关闭对服务或内容的访问
            OFFER_REDEEMED 一种通知类型，连同其subtype指示用户兑换了促销优惠或优惠代码。 subtype DID_RENEW
            PRICE_INCREASE 一种通知类型，连同其subtype指示系统已通知用户订阅价格上涨
            REFUND 表示 App Store 成功为消耗性应用内购买、非消耗性应用内购买、自动续订订阅或非续订订阅的交易退款
            REFUND_DECLINED 表示 App Store 拒绝了应用开发者发起的退款请求
            RENEWAL_EXTENDED 表示 App Store 延长了开发者要求的订阅续订日期
            REVOKE表示 用户有权通过家庭共享获得的应用内购买不再通过共享获得
            SUBSCRIBED 一种通知类型，连同其subtype指示用户订阅了产品
            */

            $isRenew = -1;
            switch ($data['notificationType']){
                case 'SUBSCRIBED':
                case 'DID_RENEW':
                    //开通成功以及续订成功处理交易
                    $isRenew = 1;
                    break;
                case 'REFUND':
                    //用户退款处理交易
                    $isRenew = 2;
                    break;
                case 'EXPIRED':
                case 'DID_FAIL_TO_RENEW':
                    //用户取消订阅 用户还没给钱情况下是取消
                    $isRenew = 0;
                    break;
            }

            $data['transactionInfo'] = $this->verifyToken($data['data']['signedTransactionInfo']);
            $data['renewalInfo']     = isset($data['data']['signedRenewalInfo']) ? $this->verifyToken($data['data']['signedRenewalInfo']) : [];
            $data['is_renew'] = $isRenew;

        }
        //todo 记录通知log
        $insert = $data;
        $insert['notify_content'] = $postDataContent;
        $insert['signedpayload'] = $data;
        ApplePayNotifyLogModel::getInstance()->insertData($insert);

        return $data;
    }


    /**
     * 验证token是否有效,默认验证exp,nbf,iat时间
     * @param string $Token 需要验证的token
     * @return bool|string
     */
    public static function verifyToken($Token)
    {
        $tokens = explode('.', $Token);
        if (count($tokens) != 3) {
            return false;
        }

        list($base64header, $base64payload) = $tokens;

        //获取jwt算法
        $base64decodeheader = json_decode(self::base64UrlDecode($base64header), JSON_OBJECT_AS_ARRAY);
        if (empty($base64decodeheader['alg']) || $base64decodeheader['alg'] != 'ES256')
            return false;

        $payload = json_decode(self::base64UrlDecode($base64payload), JSON_OBJECT_AS_ARRAY);

        return $payload;
    }

    /**
     * base64UrlEncode   https://jwt.io/  中base64UrlEncode编码实现
     * @param string $input 需要编码的字符串
     * @return string
     */
    private static function base64UrlEncode($input)
    {
        return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
    }

    /**
     * base64UrlEncode  https://jwt.io/  中base64UrlEncode解码实现
     * @param string $input 需要解码的字符串
     * @return bool|string
     */
    private static function base64UrlDecode($input)
    {
        $remainder = strlen($input) % 4;
        if ($remainder) {
            $addlen = 4 - $remainder;
            $input  .= str_repeat('=', $addlen);
        }
        return base64_decode(strtr($input, '-_', '+/'));
    }

}
